/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include <set>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"
#include "BTreeNode.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);

/*
 * Helper function
 */
void printValues(int attr, int key, string value, int count)
{
    switch (attr) {
    case 1:  // SELECT key
      fprintf(stdout, "%d\n", key);
      break;
    case 2:  // SELECT value
      fprintf(stdout, "%s\n", value.c_str());
      break;
    case 3:  // SELECT *
      fprintf(stdout, "%d '%s'\n", key, value.c_str());
      break;
	case 4:  // SELECT count(*)
      fprintf(stdout, "%d\n", count);
      break;
    }

}

RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

typedef struct{
	int key;
	SelCond::Comparator comp;
} range;

typedef struct{
	char* value;
	SelCond::Comparator comp;
} vcond;


void insertToRange( range r, vector<range> &vR){

	vector<range>::iterator it;

	it = vR.begin();
	for(int i = 0; i<vR.size(); i++){		
		if(r.key <= vR[i].key){
			vR.insert(it + i,r);
			return;
		}
	}
	vR.push_back(r);
}

void insertToConds( vcond c, vector<vcond> &vC){

	vector<vcond>::iterator it;

	it = vC.begin();
	for(int i = 0; i<vC.size(); i++){
		if((strcmp(c.value,vC[i].value))<=0){
			vC.insert(it+i,c);
			return;
		}
	}
	vC.push_back(c);
}

bool applyConds(char* start, int stype, char* end, int etype, char* equal, bool isEqual, const char* value){
	bool result = true;
	if(isEqual)
		result = result && ((strcmp(value, equal))==0);
	if(stype == 1)
		result = result && ((strcmp(value, start)) > 0);
	if(stype == 2)
		result = result && ((strcmp(value, start))>=0);
	if(etype == 1)
		result = result && ((strcmp(value, end)) < 0);
	if(etype == 2)
		result = result && ((strcmp(value, end)) <= 0);
	
	return result;
}


RC SqlEngine::oselect(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning

  RC     rc;
  int    key;     
  string value;
  int    count;
  int    diff;

  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }

  // scan the table file from the beginning
  rid.pid = rid.sid = 0;
  count = 0;
  while (rid < rf.endRid()) {
    // read the tuple
    if ((rc = rf.read(rid, key, value)) < 0) {
      fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
      goto exit_select;
    }

    // check the conditions on the tuple
    for (unsigned i = 0; i < cond.size(); i++) {
      // compute the difference between the tuple value and the condition value
      switch (cond[i].attr) {
      case 1:
	diff = key - atoi(cond[i].value);
	break;
      case 2:
	diff = strcmp(value.c_str(), cond[i].value);
	break;
      }

      // skip the tuple if any condition is not met
      switch (cond[i].comp) {
      case SelCond::EQ:
	if (diff != 0) goto next_tuple;
	break;
      case SelCond::NE:
	if (diff == 0) goto next_tuple;
	break;
      case SelCond::GT:
	if (diff <= 0) goto next_tuple;
	break;
      case SelCond::LT:
	if (diff >= 0) goto next_tuple;
	break;
      case SelCond::GE:
	if (diff < 0) goto next_tuple;
	break;
      case SelCond::LE:
	if (diff > 0) goto next_tuple;
	break;
      }
    }

    // the condition is met for the tuple. 
    // increase matching tuple counter
    count++;

    // print the tuple 
    switch (attr) {
    case 1:  // SELECT key
      fprintf(stdout, "%d\n", key);
      break;
    case 2:  // SELECT value
      fprintf(stdout, "%s\n", value.c_str());
      break;
    case 3:  // SELECT *
      fprintf(stdout, "%d '%s'\n", key, value.c_str());
      break;
    }

    // move to the next tuple
    next_tuple:
    ++rid;
  }

  // print matching tuple count if "select count(*)"
  if (attr == 4) {
    fprintf(stdout, "%d\n", count);
  }
  rc = 0;

  // close the table file and return
  exit_select:
  rf.close();
  return rc;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning
  BTreeIndex tempTree;
  string btbl =  table + ".idx";
  
  RC     rc;
  int    key = 0;     
  string value = "";
  int    count;

  IndexCursor cursor;
  vector<range> mRange;
  vector<vcond> mValue;
  // open the table file


  //see if BTree table exists
  if((rc=tempTree.open(btbl, 'r'))!=0)
	return oselect(attr, table, cond);	

  rid.pid = rid.sid = 0;
  count = 0;
//divide conds 
  for(int i =0; i<cond.size(); i++){
		if(cond[i].attr == 1){
			range r;
			r.key = atoi(cond[i].value);
			r.comp = cond[i].comp;
			insertToRange(r,mRange);
		}else{
			vcond v;
			v.value = cond[i].value;
			v.comp = cond[i].comp;
			insertToConds(v,mValue);
		}
	}
	
	if((mRange.size() == 0)&& (mValue.size() > 0)){
		tempTree.close();
		return oselect(attr, table, cond);	
	}
	
/////Calculate ranges
	int startk = 0; 	//start key
	bool includesk = false;
	bool fstartk = false;   //found start key
	int endk = 0;		//end key
	bool fendk = false;		//found end key
	bool includeek = false;
	
	char* startv;	//start value
	bool fstartv = false;	//found start value
	bool includesv = false;
	char* endv; 
	bool fendv = false;
	bool includeev = false;
	 bool printIt = true;
	set<int> skipk;
	set<char*> skipv;

	int equalk = 0;
	bool fequalk = false;
	char* equalv;
	bool fequalv = false;

//EQ, NE, LT, GT, LE, GE
	for(int i =0; i<mRange.size();i++){
		switch(mRange[i].comp){
			case SelCond::EQ:
				if(fequalk && equalk!=mRange[i].key){
					goto exit_select2;
				}
				fequalk = true;
				equalk = mRange[i].key;
				break;
			
			case SelCond::NE:
				skipk.insert(mRange[i].key);
				break;
				
			case SelCond::LE:
				if(!fendk)
					includeek = true;
			case SelCond::LT:
				if(fendk)
					break;
				fendk = true;
				endk = mRange[i].key;
				break;
			
			case SelCond::GE:
				includesk = true;
			case SelCond::GT:
				if(fendk){
					goto exit_select2;
				}
				if(mRange[i].comp != SelCond::GE)
					includesk = false;
				fstartk = true;
				startk = mRange[i].key;
				break;
			default:
				break;
		}
	}
  if(fequalk){
	if(fstartk && ((equalk < startk) || ((!includesk) && (equalk == startk))))
		goto exit_select2;
	if(fendk && ((equalk > endk) || ((!includeek) && (equalk == endk))))
		goto exit_select2;
	if( skipk.find(equalk) != skipk.end())
		goto exit_select2;
  }
  if(!startk && !endk){
	if(!equalk && (skipk.size()>0)){
		tempTree.close();
		return oselect(attr, table, cond);		
		}
  }
 /////////finding value constraints
 	for(int i =0; i<mValue.size();i++){
		switch(mValue[i].comp){
			case SelCond::EQ:
				if(fequalv && equalv!=mValue[i].value){
					goto exit_select2;
				}
				fequalv = true;
				equalv = mValue[i].value;
				break;
			
			case SelCond::NE:
				skipv.insert(mValue[i].value);
				break;
				
			case SelCond::LE:
				if(!fendv)
					includeev = true;
			case SelCond::LT:
				if(fendv)
					break;
				fendv = true;
				endv = mValue[i].value;
				break;
			
			case SelCond::GE:
				includesv = true;
			case SelCond::GT:
				if(fendv){
					goto exit_select2;
				}
				if(mValue[i].comp != SelCond::GE)
					includesv = false;
				fstartv = true;
				startv = mValue[i].value;
				break;
			default:
				break;
		}
	}
  if(fequalv){
	if(fstartv && (((strcmp(equalv,startv))<=0)|| ((!includesv) && ((strcmp(equalv,startv))==0))))
		goto exit_select2;
	if(fendv && (((strcmp(equalv,endv))>=0)|| ((!includeev) && ((strcmp(equalv,endv))==0))))
		goto exit_select2;
	if( skipv.find(equalv) != skipv.end())
		goto exit_select2;
  }
  if(!startv && !endv){
	if(!equalv && (skipv.size()>0)){
		tempTree.close();
		return oselect(attr, table, cond);
	}
  }
  
  
  /// 
 ///////////////////////////print Results
 
   if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }
 
 if(fequalk){
  	if((rc = tempTree.locate(equalk,cursor))<0){
		fprintf(stderr, "Error1: while reading a tuple from B+ tree %s\n", table.c_str());
		goto exit_select2;
	}
	if ((rc = tempTree.readForward(cursor,key,rid)) < 0) {
		fprintf(stderr, "Error2: while reading a tuple from B+ tree %s\n", table.c_str());
		goto exit_select2;
    }
	printIt = true;
	if( (attr == 2) || (attr == 3) || (mValue.size() > 0)){
		if ((rc = rf.read(rid, key, value)) < 0) {
			fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
			goto exit_select2;
		}
		int etype = 0;
		if(fendv)
			etype++;
		if(includeev)
			etype++;
		int stype = 0;
		if(fstartv)
			stype++;
		if(includesv)
			stype++;
		printIt = applyConds(startv, stype, endv, etype, equalv, fequalv, value.c_str());		
	}
	if(printIt)
		printValues(attr, equalk, value, 1);
	}

	else{
		 
		if(fstartk){
			if((rc = tempTree.locate(startk,cursor))<0){
				fprintf(stderr, "Error: while reading a tuple from B+ tree %s\n", table.c_str());
				goto exit_select2;
			}
		}else{
			if((rc = tempTree.locate(-1000,cursor))<0){
				fprintf(stderr, "Error: while reading a tuple from B+ tree %s\n", table.c_str());
				goto exit_select2;
			}			
		}
		RC checkEnd;
		if ((checkEnd = tempTree.readForward(cursor,key,rid)) < 0) {
			fprintf(stderr, "Error: while reading a tuple from B+ tree %s\n", table.c_str());
			goto exit_select2;
		}
		if((fstartk && (key == startk)) && !includesk){
			if ((checkEnd = tempTree.readForward(cursor,key,rid)) < 0) {
				fprintf(stderr, "Error: while reading a tuple from B+ tree %s\n", table.c_str());
				goto exit_select2;
			}
		}
			
		while((fendk && (key < endk)) || (!fendk && (checkEnd>=0))){
		    printIt = true;
			if( (attr == 2) || (attr == 3) || (mValue.size() > 0)){
				if ((rc = rf.read(rid, key, value)) < 0) {
					fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
					goto exit_select2;
				}
				int etype = 0;
				if(fendv)
					etype++;
				if(includeev)
					etype++;
				int stype = 0;
				if(fstartv)
					stype++;
				if(includesv)
					stype++;
				printIt = applyConds(startv, stype, endv, etype, equalv, fequalv, value.c_str());		
			}
			count++;
			if(printIt  && attr != 4)
				printValues(attr, key, value, count);
			
				checkEnd = tempTree.readForward(cursor,key,rid);
			
		}
		if(checkEnd<0){
			  printIt = true;
			if( (attr == 2) || (attr == 3) || (mValue.size() > 0)){
				if ((rc = rf.read(rid, key, value)) < 0) {
					fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
					goto exit_select2;
				}
				int etype = 0;
				if(fendv)
					etype++;
				if(includeev)
					etype++;
				int stype = 0;
				if(fstartv)
					stype++;
				if(includesv)
					stype++;
				printIt = applyConds(startv, stype, endv, etype, equalv, fequalv, value.c_str());		
			}
			count++;
			if(printIt  && attr != 4)
				printValues(attr, key, value, count);
			}
			if(attr == 4)
				printValues(attr, key, value, count);
			
	}
  
  
  
  //key variables
  rf.close();
  tempTree.close();
  return 0;
  
  exit_select2:
  tempTree.close();
  return rc;
}
/*
 * Include Indexing from Part D
 */
RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
  //initial values, index default false if not specified
  string line, value, temptbl, btemptbl;
  int key;
  temptbl = "./"+ table + ".tbl";
  btemptbl = "./"+table+".idx";
  ifstream fin;
  RecordFile newRecord;
  RecordId rid;
  RC rc;
  //create empty B+tree index
  BTreeIndex tempTree;
  //open file to read
  fin.open(loadfile.c_str());

  //load index if true
  if (index)
  {
      if((rc=tempTree.open(btemptbl, 'w'))<0)
          return rc;
  }
  //open file to write
  if ((rc=newRecord.open(temptbl, 'w'))<0)
  {
  //    fprintf(stderr, "\nError: Cannot open table to write.\n");
      return rc;  
  }
  if(fin.is_open())
  { //read each line
    while(getline(fin, line))
    { //parse each line
        parseLoadLine(line, key, value);
        newRecord.append(key, value, rid);
        if(index){
			
            rc = tempTree.insert(key, rid);

		}
    }
  }
  else
  {
    fprintf(stderr, "Error: Cannot open file.\n");
    return 1;
  }
 

  fin.close();
  if(index)
      tempTree.close();
  newRecord.close();
  return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
